故事要从小明的离职说起:

一天小明突然离职,留下了一个待上线的项目给我,除了附赠一堆样式优化、些许bug外,还额外赠送了一个奇怪的性能问题,而这个额外赠品足足让我享用了一天。

其实项目很简单,只有一个Vue实现的页面,头部是筛选条件,中间是数据表格(分页,数据量级为千),底部展示表格数据对应的可视化折线图(展示全部数据,即量级为千)。性能现象也很简单,接口获取数据到表格展示数据中间耗时平均达1.5秒以上,即1.5秒的白屏等待。

首先查看了页面渲染这部分的代码,先是根据筛选条件查询数据,然后将数据交由vue进行表格渲染,最后将数据交由echart进行折线图渲染,伪代码大致如下:

`
async renderPage() {
    const data = await this.getTableDataByFilter()
    this.tableData = data
    this.renderChartByTableData()
}
`
分析代码,获取数据后赋值 this.tableData,根据Vue的数据驱动机制,会渲染表格,然后才渲染折线图。

再次观察页面渲染效果,发现现象:在白屏等待后,表格和折线图几乎同时渲染出来。思考:千量级数据的echart渲染,如果像前文分析的先渲染表格再渲染echart,那么不会几乎同时渲染出来,折线图必然会比表格晚一点,莫非折线图早于表格的渲染了?

基于上边的思考,查看实际渲染先后,发现折线图果真在表格前渲染,但是为什么呢?

我开始从Vue的视图更新机制思考:Vue在数据更新后不会立即刷新视图,而是在下一个事件循环‘tick’时才会一次性更新视图。加之,echarts的渲染函数“setOption”是同步函数,也就是说,数据更新后就会立即执行“setOption”渲染折线图。这样,就很清楚问题发生的原因了,但如何解决这个问题呢?

Javascript单线程的特性,决定了不可能同时渲染表格和折线图,既然先渲染折线图会给用户页面长时间无响应的感觉,那就先渲染表格吧,毕竟表格渲染远比折线图耗时小。

那么如何保证先渲染下一次事件循环的表格,后渲染当前线程的折线图呢?Vue 的 nextTick 用于在下一次‘tick’触发回调,刚好可以实现该场景。
`
async renderPage() {
    const data = await this.getTableDataByFilter()
    this.tableData = data
    this.$nextTick(() => {
        this.renderChartByTableData()
    })
}
`

综上,这是一个由于事件循环机制导致的不易被觉察的渲染优先级问题,问题得解。
最后更新时间: 4/27/2021, 7:11:19 PM